import * as THREE from "three";
import * as CANNON from "cannon-es";
import _ from "lodash";
import * as Utils from "../core/FunctionLibrary";

import { KeyBinding } from "../core/KeyBinding";
import { VectorSpringSimulator } from "../physics/spring_simulation/VectorSpringSimulator";
import { Idle } from "./character_states/Idle";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import { ICharacterEntity } from "../interface/IWorldEntity";
import { EntityType } from "../enums/EntityType";
import { CapsuleParams } from "../interface/CapsuleParams";
import { CapsuleCollider } from "../physics/colliders/CapsuleCollider";
import { CollisionGroups } from "../enums/CollisionGroups";
import { RelativeSpringSimulator } from "../physics/spring_simulation/RelativeSpringSimulator";
import { ICharacterState } from "../interface/ICharacterState";
import { ICharacterAI } from "../interface/ICharacterAI";
import { World } from "../world/World";
import { IInputReceiver } from "../interface/IInputReceiver";
import { IControllable } from "../interface/IControllable";

export class Character extends THREE.Object3D implements ICharacterEntity {

    public updateOrder = 1;
    public entityType = EntityType.Character;

    public titleContainer = new THREE.Group();
    public modelContainer = new THREE.Group();
    public materials: THREE.Material[] = [];
    public mixer: THREE.AnimationMixer;

    // movement
    public acceleration = new THREE.Vector3();
    public velocity = new THREE.Vector3();
    public arcadeVelocityInfluence = new THREE.Vector3();
    public velocityTarget = new THREE.Vector3();
    public arcadeVelocityIsAdditive = false;

    // movement simulator
    public defaultVelocitySimulatorDamping = 0.8;
    public defaultVelocitySimulatorMass = 50;
    public velocitySimulator: VectorSpringSimulator;
    public moveSpeed = 0.4;
    public angularVelocity = 0;
    public orientation = new THREE.Vector3( 0, 0, 1 );
    public orientationTarget = new THREE.Vector3( 0, 0, 1 );
    public defaultRotationSimulatorDamping = 0.5;
    public defaultRotationSimulatorMass = 10;
    public rotationSimulator: RelativeSpringSimulator;
    public viewVector = new THREE.Vector3();

    public actions: { [ key: string ]: KeyBinding; };
    public characterCapsule: CapsuleCollider;


    // 射线检测
    public rayResult: CANNON.RaycastResult;
    public rayHasHit: boolean;
    public rayCastLength: number;
    public raySafeOffset: number;
    public wantsToJump = false;
    public initJumpSpeed = -1;
    public groundImpactData;
    public raycastBox: THREE.Mesh;

    public world: World;
    public characterState: ICharacterState;
    public behavior: ICharacterAI;

    // 载具
    public controlledObject: IControllable;

    private physicsEnabled = true;

    constructor ( gltf: GLTF ) {
        super();

        this.add( this.titleContainer );
        this.modelContainer.position.y = -0.57;
        this.titleContainer.add( this.modelContainer );
        this.modelContainer.add( gltf.scene );

        this.mixer = new THREE.AnimationMixer( gltf.scene );

        this.readCharacterData( gltf );
        this.setAnimations( gltf.animations );

        this.velocitySimulator = new VectorSpringSimulator( 60, this.defaultVelocitySimulatorMass, this.defaultVelocitySimulatorDamping );
        this.rotationSimulator = new RelativeSpringSimulator( 60, this.defaultRotationSimulatorMass, this.defaultRotationSimulatorDamping );


        // 动作 
        this.actions = {
            up: new KeyBinding( 'KeyW' ),
            down: new KeyBinding( 'KeyS' ),
            left: new KeyBinding( 'KeyA' ),
            right: new KeyBinding( 'KeyD' ),
            run: new KeyBinding( 'ShiftLeft' ),
            jump: new KeyBinding( 'Space' ),
            use: new KeyBinding( 'KeyE' ),
            enter: new KeyBinding( 'KeyF' ),
            enter_passenger: new KeyBinding( 'KeyG' ),
            seat_switch: new KeyBinding( 'KeyX' ),
            primary: new KeyBinding( 'Mouse0' ),
            secondary: new KeyBinding( 'Mouse1' ),
        };

        // 物理性质
        // 人物胶囊体
        this.characterCapsule = new CapsuleCollider( {
            mass: 1,
            position: new CANNON.Vec3(),
            height: 0.5,
            radius: 0.25,
            segments: 8,
            friction: 0.0
        } );

        // 碰撞过滤
        this.characterCapsule.body.shapes.forEach( shape => {
            shape.collisionFilterMask = ~CollisionGroups.TrimeshColliders;
        } );

        this.characterCapsule.body.allowSleep = false;

        // 将角色移动到光线投射的不同碰撞组
        this.characterCapsule.body.collisionFilterGroup = CollisionGroups.Characters;

        // 禁止角色碰撞体旋转
        this.characterCapsule.body.fixedRotation = true;
        this.characterCapsule.body.updateMassProperties();

        // 射线检测BOX
        const boxGeometry = new THREE.BoxGeometry( 0.1, 0.1, 0.1 );
        const boxMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
        this.raycastBox = new THREE.Mesh( boxGeometry, boxMaterial );
        this.raycastBox.visible = false;

        this.characterCapsule.body.preStep = ( body ) => { };
        this.characterCapsule.body.postStep = ( body ) => { };

        this.setState( new Idle( this ) );
    }



    public addToWorld ( world: World ): void {

        if ( _.includes( world.characters, this ) ) {

            console.warn( "该角色在世界中已经存在" );

        } else {

            this.world = world;
            world.characters.push( this );
            world.physicsWorld.addBody( this.characterCapsule.body );

            world.graphicsWorld.add( this );
            world.graphicsWorld.add( this.raycastBox );

            this.materials.forEach( material => {
                world.sky.csm.setupMaterial( material );
            } );
        }
    };

    public removeFromWorld (): void {

    };

    public update ( timeStep: number, unscaledTimeStep: number ): void {

        this.behavior?.update( timeStep );


        if ( this.physicsEnabled ) {
            this.position.copy( this.characterCapsule.body.interpolatedPosition as unknown as THREE.Vector3 );
        } else {

            const position = new THREE.Vector3();
            this.getWorldPosition( position );

            this.characterCapsule.body.position.copy( position as unknown as CANNON.Vec3 );
            this.characterCapsule.body.interpolatedPosition.copy( position as unknown as CANNON.Vec3 );

        }

        this.updateMatrixWorld();

    }

    public setState ( state: ICharacterState ): void {

        this.characterState = state;
        this.characterState.onInputChange();

    }


    /**@description 设置坐标 */
    public setPosition ( x: number, y: number, z: number ): void {

        if ( this.physicsEnabled ) {

            this.characterCapsule.body.previousPosition = new CANNON.Vec3( x, y, z );
            this.characterCapsule.body.position = new CANNON.Vec3( x, y, z );
            this.characterCapsule.body.interpolatedPosition = new CANNON.Vec3( x, y, z );

        } else {

            this.position.set( x, y, z );

        }

    }

    public setArcadeVelocityTarget () {

    }

    public setArcadeVelocityInfluence ( x: number, y: number = x, z: number = x ) {

        this.arcadeVelocityInfluence.set( x, y, z );

    }

    /**@description 设置方向 */
    public setOrientation ( vector: THREE.Vector3, instantly = false ): void {

        const lookVector = new THREE.Vector3().copy( vector ).setY( 0 ).normalize();
        this.orientationTarget.copy( lookVector );

        if ( instantly ) {
            this.orientation.copy( lookVector );
        }
    }

    public readCharacterData ( gltf: GLTF ): void {

        gltf.scene.traverse( child => {

            if ( child instanceof THREE.Mesh ) {

                Utils.setupMeshProperties( child );

                if ( child.material !== undefined ) {

                    this.materials.push( child.material );

                }
            }

        } );

    }

    public setAnimations ( animations: THREE.AnimationClip[] ): void {
        this.animations = animations;
    }

    public inputReceiverInit (): void {

        if ( this.controlledObject !== undefined ) {

            this.controlledObject.inputReceiverInit();
            return;

        }

        this.world.cameraOperator.setRadius( 1.6, true );
        this.world.cameraOperator.followMode = false;

        this.displayControls();
    }

    public displayControls () {
        this.world.updateControls( [
            {
                keys: [ 'W', 'A', 'S', 'D' ],
                desc: 'Movement'
            },
            {
                keys: [ 'Shift' ],
                desc: 'Sprint'
            },
            {
                keys: [ 'Space' ],
                desc: 'Jump'
            },
            {
                keys: [ 'F', 'or', 'G' ],
                desc: 'Enter vehicle'
            },
            {
                keys: [ 'Shift', '+', 'R' ],
                desc: 'Respawn'
            },
            {
                keys: [ 'Shift', '+', 'C' ],
                desc: 'Free camera'
            },
        ] );
    }

    public inputReceiverUpdate ( timeStep: number ): void {

        if ( this.controlledObject !== undefined ) {
            this.controlledObject.inputReceiverUpdate( timeStep );
        } else {

            this.viewVector = new THREE.Vector3().subVectors( this.position, this.world.camera.position );
            this.getWorldPosition( this.world.cameraOperator.target );

        }

    }

    public execute ( code: string, pressed: boolean ) {

        const keys = Reflect.ownKeys( this.actions ) as Array<string>;

        for ( let i = 0, length = keys.length; i < length; i++ ) {
            const key = keys[ i ];
            const binding = this.actions[ key ];

            if ( _.includes( binding.eventCodes, code ) ) {

                this.triggerAction( key, pressed );

            }

        }
    }

    public handleKeyboardEvent ( event: KeyboardEvent, code: string, pressed: boolean ): void {

        if ( this.controlledObject !== undefined ) {
            // 如果角色的控制对象存在，意味着在开车，执行车实例的事件
            this.controlledObject.handleKeyboardEvent( event, code, pressed );

        } else {

            if ( code === "KeyC" && pressed === true && event.shiftKey === true ) {



            } else if ( code === "KeyR" && pressed === true && event.shiftKey === true ) {



            } else {

                this.execute( code, pressed );

            }

        }

    }



    public handleMouseButton ( event: MouseEvent, code: string, pressed: boolean ): void {

        if ( this.controlledObject !== undefined ) {
            this.controlledObject.handleMouseButton( event, code, pressed );
        } else {

            this.execute( code, pressed );

        }

    }

    public handleMouseMove ( event: MouseEvent, deltaX: number, deltaY: number ): void {

        if ( this.controlledObject !== undefined ) {

            this.controlledObject.handleMouseMove( event, deltaX, deltaY );

        } else {

            this.world.cameraOperator.move( deltaX, deltaY );

        }

    }

    public handleMouseWheel ( event: WheelEvent, value: number ): void {

        if ( this.controlledObject !== undefined ) {

            this.controlledObject.handleMouseWheel( event, value );

        } else {

            this.world.scrollTheTimeScale( value );

        }
    }

    public triggerAction ( actionName: string, pressed: boolean ): void {

        const action = this.actions[ actionName ];

        if ( action.isPressed !== pressed ) {

            action.isPressed = pressed;

            action.justPressed = false;
            action.justReleased = false;

            if ( pressed ) action.justPressed = true;
            else action.justReleased = true;

            this.characterState.onInputChange();

            action.justPressed = false;
            action.justReleased = false;

        }
    }

    public resetControls (): void {

        const keys = Reflect.ownKeys( this.actions );

        for ( let i = 0, length = keys.length; i < length; i++ ) {

            const key = keys[ i ] as string;
            this.triggerAction( key, false );

        }

    }

    /** 注册角色控制器 */
    public takeControl (): void {

        if ( this.world !== undefined ) {
            this.world.inputManager.setInputReceiver( this );
        } else {
            console.warn( "警告：尝试去控制一个不在世界的角色" );
        }

    }

}